//  MOEAD.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
package jmetal.metaheuristics.moead;

import jmetal.core.*;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.Vector;
import jmetal.metaheuristics.moeansm.util.TempoExecucao;
import jmetal.metaheuristics.moeansm.util.UtilAP;
import jmetal.qualityIndicator.QualityIndicator;

public class MOEADBuscaLocal extends Algorithm {

    private int populationSize_;
    /**
     * Stores the population
     */
    private SolutionSet population_;
    /**
     * Z vector (ideal point)
     */
    double[] z_;
    /**
     * Lambda vectors
     */
    //Vector<Vector<Double>> lambda_ ; 
    double[][] lambda_;
    /**
     * T: neighbour size
     */
    int T_;
    /**
     * Neighborhood
     */
    int[][] neighborhood_;
    /**
     * delta: probability that parent solutions are selected from neighbourhood
     */
    double delta_;
    /**
     * nr: maximal number of solutions replaced by each child solution
     */
    int nr_;
    Solution[] indArray_;
    String functionType_;
    int evaluations_;
    /**
     * Operators
     */
    Operator crossover_;
    Operator mutation_;
    Operator buscaLocalOperator;

    String dataDirectory_;

    /**
     * Constructor
     *
     * @param problem Problem to solve
     */
    public MOEADBuscaLocal(Problem problem) {
        super(problem);

        functionType_ = "_TCHE1";

    } // DMOEA

    public SolutionSet execute() throws JMException, ClassNotFoundException {
        int maxEvaluations;

        TempoExecucao tempoExecucao = new TempoExecucao();

        /*ESTAGNACAO*/
        int estagnacao = 0;
        int MAX_ESTAGNACAO = 200;
        double HV = 0.0;

        QualityIndicator indicators; // QualityIndicator object
        evaluations_ = 0;
        maxEvaluations = ((Integer) this.getInputParameter("maxEvaluations")).intValue();
        populationSize_ = ((Integer) this.getInputParameter("populationSize")).intValue();
        dataDirectory_ = this.getInputParameter("dataDirectory").toString();
        indicators = (QualityIndicator) getInputParameter("indicators");
        System.out.println("POPSIZE: " + populationSize_);

        population_ = new SolutionSet(populationSize_);
        indArray_ = new Solution[problem_.getNumberOfObjectives()];

        T_ = ((Integer) this.getInputParameter("T")).intValue();//quantidade de vizinhos de cada solucao  - Vizinhanca
        nr_ = ((Integer) this.getInputParameter("nr")).intValue(); //maximo numero de solucoes substituidas por cada solucao filha
        delta_ = ((Double) this.getInputParameter("delta")).doubleValue();//probabilidade de solucoes pais serem selecionadas de solucoes vizinhas

        /*
         T_ = (int) (0.1 * populationSize_);
         delta_ = 0.9;
         nr_ = (int) (0.01 * populationSize_);
         */
        neighborhood_ = new int[populationSize_][T_];

        z_ = new double[problem_.getNumberOfObjectives()];
        //lambda_ = new Vector(problem_.getNumberOfObjectives()) ;
        lambda_ = new double[populationSize_][problem_.getNumberOfObjectives()];

        crossover_ = operators_.get("crossover"); // default: DE crossover
        mutation_ = operators_.get("mutation");  // default: polynomial mutation
        buscaLocalOperator = operators_.get("buscaLocal");

        // STEP 1. Initialization
        // STEP 1.1. Compute euclidean distances between weight vectors and find T
        initUniformWeight();
        //for (int i = 0; i < 300; i++)
        // 	System.out.println(lambda_[i][0] + " " + lambda_[i][1]) ;

        initNeighborhood();

        // STEP 1.2. Initialize population
        initPopulation();

        // STEP 1.3. Initialize z_
        initIdealPoint();

        // STEP 2. Update
        do {
            int[] permutation = new int[populationSize_];
            Utils.randomPermutation(permutation, populationSize_);

            for (int i = 0; i < populationSize_; i++) {
                int n = permutation[i]; // or int n = i;
                //int n = i ; // or int n = i;
                int type;
                double rnd = PseudoRandom.randDouble();

                // STEP 2.1. Mating selection based on probability
                if (rnd < delta_) // if (rnd < realb)    
                {
                    type = 1;   // neighborhood
                } else {
                    type = 2;   // whole population
                }
                Vector<Integer> p = new Vector<Integer>();
                matingSelection(p, n, 2, type);

                // STEP 2.2. Reproduction
                Solution child, child2;
                Solution[] parents = new Solution[3];

                parents[0] = population_.get(p.get(0));
                parents[1] = population_.get(p.get(1));
                parents[2] = population_.get(n);

                child = ((Solution[]) crossover_.execute(parents))[0];
                child2 = ((Solution[]) crossover_.execute(parents))[1];

                int objetivoSelecionado = UtilAP.obterNumeroAleatorio(0, 2);
                child.setObjetivoBuscaLocal(objetivoSelecionado);
                child.setInstancia((objetivoSelecionado == 0) ? problem_.instancia1 : problem_.instancia2);
                child = (Solution) buscaLocalOperator.execute(child);

                objetivoSelecionado = UtilAP.obterNumeroAleatorio(0, 2);
                child2.setObjetivoBuscaLocal(objetivoSelecionado);
                child2.setInstancia((objetivoSelecionado == 0) ? problem_.instancia1 : problem_.instancia2);
                child2 = (Solution) buscaLocalOperator.execute(child2);

                // Apply DE crossover 
//        child = (Solution) crossover_.execute(new Object[]{population_.get(n), parents});
                // Apply mutation
                mutation_.execute(child);
                mutation_.execute(child2);//OBSERVAR, ANTES SO EXISTIA child1 

                // Evaluation
                problem_.evaluate(child);
                problem_.evaluate(child2);   //OBSERVAR, ANTES SO EXISTIA child1    

                evaluations_++;
                evaluations_++;

                // STEP 2.3. Repair. Not necessary
                // STEP 2.4. Update z_
                updateReference(child);
                updateReference(child2);//OBSERVAR, ANTES SO EXISTIA child1 

                // STEP 2.5. Update of solutions
                updateProblem(child, n, type);
                updateProblem(child2, n, type);//OBSERVAR, ANTES SO EXISTIA child1 

                if (indicators != null) {
                    double HVAtual = indicators.getHypervolume(population_);
                    if (HV >= HVAtual && HV != 0.0) {
                        estagnacao++;
                    } else {
                        HV = HVAtual;
                        estagnacao = 0;
                    }
                    if (estagnacao >= MAX_ESTAGNACAO) {
                        System.out.println("MOEAD BL Estagnou na geracao: " + evaluations_);
                        break;
                    }
                } // if

            } // for 
        } while (evaluations_ < maxEvaluations);

        population_.setTempoExecucao(tempoExecucao.getTempo());
        population_.setGeracaoParou(evaluations_);

        return population_;
    }

    /**
     * initUniformWeight
     */
    public void initUniformWeight() {
        if ((problem_.getNumberOfObjectives() == 2) && (populationSize_ <= 300)) {
            for (int n = 0; n < populationSize_; n++) {
                double a = 1.0 * n / (populationSize_ - 1);
                lambda_[n][0] = a;
                lambda_[n][1] = 1 - a;
            } // for
        } // if
        else {
            String dataFileName;
            dataFileName = "W" + problem_.getNumberOfObjectives() + "D_"
                    + populationSize_ + ".dat";

            try {
                // Open the file
                FileInputStream fis = new FileInputStream(dataDirectory_ + "/" + dataFileName);
                InputStreamReader isr = new InputStreamReader(fis);
                BufferedReader br = new BufferedReader(isr);

                int numberOfObjectives = 0;
                int i = 0;
                int j = 0;
                String aux = br.readLine();
                while (aux != null) {
                    StringTokenizer st = new StringTokenizer(aux);
                    j = 0;
                    numberOfObjectives = st.countTokens();
                    while (st.hasMoreTokens()) {
                        double value = (new Double(st.nextToken())).doubleValue();
                        lambda_[i][j] = value;
                        //System.out.println("lambda["+i+","+j+"] = " + value) ;
                        j++;
                    }
                    aux = br.readLine();
                    i++;
                }
                br.close();
            } catch (Exception e) {
                System.out.println("initUniformWeight: failed when reading for file: " + dataDirectory_ + "/" + dataFileName);
                e.printStackTrace();
            }
        } // else

        //System.exit(0) ;
    } // initUniformWeight

    /**
     *
     */
    public void initNeighborhood() {
        double[] x = new double[populationSize_];
        int[] idx = new int[populationSize_];

        for (int i = 0; i < populationSize_; i++) {
            // calculate the distances based on weight vectors
            for (int j = 0; j < populationSize_; j++) {
                x[j] = Utils.distVector(lambda_[i], lambda_[j]);
                //x[j] = dist_vector(population[i].namda,population[j].namda);
                idx[j] = j;
                //System.out.println("x["+j+"]: "+x[j]+ ". idx["+j+"]: "+idx[j]) ;
            } // for

            // find 'niche' nearest neighboring subproblems
            Utils.minFastSort(x, idx, populationSize_, T_);
            //minfastsort(x,idx,population.size(),niche);

            System.arraycopy(idx, 0, neighborhood_[i], 0, T_);
        } // for
    } // initNeighborhood

    /**
     *
     */
    public void initPopulation() throws JMException, ClassNotFoundException {
        for (int i = 0; i < populationSize_; i++) {
            Solution newSolution = new Solution(problem_);

//            int objetivoSelecionado = UtilAP.obterNumeroAleatorio(0, 2);
//            newSolution.setObjetivoBuscaLocal(objetivoSelecionado);
//            newSolution.setInstancia((objetivoSelecionado == 0) ? problem_.instancia1 : problem_.instancia2);
//            newSolution = (Solution) buscaLocalOperator.execute(newSolution);
            problem_.evaluate(newSolution);
            evaluations_++;
            population_.add(newSolution);
        } // for
    } // initPopulation

    /**
     *
     */
    void initIdealPoint() throws JMException, ClassNotFoundException {
        for (int i = 0; i < problem_.getNumberOfObjectives(); i++) {
            z_[i] = 1.0e+30;
            indArray_[i] = new Solution(problem_);
            problem_.evaluate(indArray_[i]);
            evaluations_++;
        } // for

        for (int i = 0; i < populationSize_; i++) {
            updateReference(population_.get(i));
        } // for
    } // initIdealPoint

    /**
     *
     */
    public void matingSelection(Vector<Integer> list, int cid, int size, int type) {
        // list : the set of the indexes of selected mating parents
        // cid  : the id of current subproblem
        // size : the number of selected mating parents
        // type : 1 - neighborhood; otherwise - whole population
        int ss;
        int r;
        int p;

        ss = neighborhood_[cid].length;
        while (list.size() < size) {
            if (type == 1) {
                r = PseudoRandom.randInt(0, ss - 1);
                p = neighborhood_[cid][r];
                //p = population[cid].table[r];
            } else {
                p = PseudoRandom.randInt(0, populationSize_ - 1);
            }
            boolean flag = true;
            for (int i = 0; i < list.size(); i++) {
                if (list.get(i) == p) // p is in the list
                {
                    flag = false;
                    break;
                }
            }

            //if (flag) list.push_back(p);
            if (flag) {
                list.addElement(p);
            }
        }
    } // matingSelection

    /**
     *
     * @param individual
     */
    void updateReference(Solution individual) {
        for (int n = 0; n < problem_.getNumberOfObjectives(); n++) {
            if (individual.getObjective(n) < z_[n]) {
                z_[n] = individual.getObjective(n);

                indArray_[n] = individual;
            }
        }
    } // updateReference

    /**
     * @param individual
     * @param id
     * @param type
     */
    void updateProblem(Solution indiv, int id, int type) {
        // indiv: child solution
        // id:   the id of current subproblem
        // type: update solutions in - neighborhood (1) or whole population (otherwise)
        int size;
        int time;

        time = 0;

        if (type == 1) {
            size = neighborhood_[id].length;
        } else {
            size = population_.size();
        }
        int[] perm = new int[size];

        Utils.randomPermutation(perm, size);

        for (int i = 0; i < size; i++) {
            int k;
            if (type == 1) {
                k = neighborhood_[id][perm[i]];
            } else {
                k = perm[i];      // calculate the values of objective function regarding the current subproblem
            }
            double f1, f2;

            f1 = fitnessFunction(population_.get(k), lambda_[k]);
            f2 = fitnessFunction(indiv, lambda_[k]);

            if (f2 < f1) {
                population_.replace(k, new Solution(indiv));
                //population[k].indiv = indiv;
                time++;
            }
            // the maximal number of solutions updated is not allowed to exceed 'limit'
            if (time >= nr_) {
                return;
            }
        }
    } // updateProblem

    double fitnessFunction(Solution individual, double[] lambda) {
        double fitness;
        fitness = 0.0;

        if (functionType_.equals("_TCHE1")) {
            double maxFun = -1.0e+30;

            for (int n = 0; n < problem_.getNumberOfObjectives(); n++) {
                double diff = Math.abs(individual.getObjective(n) - z_[n]);

                double feval;
                if (lambda[n] == 0) {
                    feval = 0.0001 * diff;
                } else {
                    feval = diff * lambda[n];
                }
                if (feval > maxFun) {
                    maxFun = feval;
                }
            } // for

            fitness = maxFun;
        } // if
        else {
            System.out.println("MOEAD.fitnessFunction: unknown type " + functionType_);
            System.exit(-1);
        }
        return fitness;
    } // fitnessEvaluation
} // MOEAD

